// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TimerAgent.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Microsoft.Robotics.Runtime
{
    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Runtime.Serialization;
    using System.Threading;

    /// <summary>
    /// A simple producer agent that wakes up on a predefined interval and publishes a simple message.
    /// This is useful for agents that need to poll some resource. Such agents can simply subscribe to this timer agent rather than registering a timer on their own.
    /// </summary>
    [DataContract]
    public class TimerAgent : ProducerAgent<TimerMessage>, IControlReceiver<TimerControlMessage>
    {
        /// <summary>
        /// The factor used when fast forwarding 
        /// </summary>
        private const int FastForwardFactor = 2;

        /// <summary>
        /// The factor used to compute the amount to skip ahead at a time
        /// </summary>
        private const int SkipFactor = 10;

        /// <summary>
        /// Delegate we need to hold on to, so that it doesn't get garbage collected.
        /// </summary>
        private NativeMethods.TimerDelegate timerDelegate;

        /// <summary>
        /// The id of the multimedia timer we use under the covers
        /// </summary>
        private uint timerId;

        /// <summary>
        /// The interval on which to publish messages.
        /// </summary>
        [DataMember(Name = "TimerInterval")]
        private uint timerInterval;

        /// <summary>
        /// A factor that can be used to speed up or slow down time
        /// </summary>
        [DataMember(Name = "AlteringFactor")]
        private double timeAlteringFactor;

        /// <summary>
        /// The time origin, set at activation time.
        /// </summary>
        private long startTime;

        /// <summary>
        /// The current time maintained by this agent.
        /// </summary>
        private long currentRelativeTime;

        /// <summary>
        /// Whether the timer is paused or not.
        /// </summary>
        private bool isPaused;

        /// <summary>
        /// Whether the timer is skipping ahead or not.
        /// </summary>
        private bool isSkipping;

        /// <summary>
        /// Initializes a new instance of the TimerAgent class.
        /// The timer fires off messages at the rate specified  by timerInterval. 
        /// The messages are timestamp such that the time between two consecutive messages appears to be timerInterval * timeAlteringFactor.
        /// For example, timerInterval = 5 and timeAlteringFactor = 2 will result in a timer that 
        /// pretends 10 ms have passed between consecutive messages, but posts messages every 5ms in real-world time.
        /// If timerInterval = 5 and timeAlteringFactor = 0.2, the timer will pretend that consecutive messages are 1ms apart,
        /// but still fires off these messages every 5ms.
        /// </summary>
        /// <param name="name">The name of the timer.</param>
        /// <param name="timerInterval">The timer interval (in real-world time).</param>
        /// <param name="timeAlteringFactor">
        /// A factor that can be used to alter time. 
        /// A value greater than 1 will make things go faster.
        /// A value less than 1 will make things go slower. In this case, the timer interval must divide by this value.
        /// </param>
        public TimerAgent(string name, uint timerInterval, double timeAlteringFactor = 1)
            : base(name)
        {
            this.timerInterval = timerInterval;
            this.timeAlteringFactor = timeAlteringFactor;
        }

        /// <summary>
        /// Initializes the agent instance.
        /// </summary>
        /// <param name="locator">A locator that the agent can use to discover other agents.</param>
        public override void Initialize(AgentLocator locator)
        {
            base.Initialize(locator);
            this.timerDelegate = new NativeMethods.TimerDelegate(this.PublishTime);
        }

        /// <summary>
        /// Called when the agent is about to start running.
        /// </summary>
        public override void OnActivated()
        {
            base.OnActivated();
            this.startTime = AgentMessage.GetCurrentTime();
            uint ctx = 0;
            this.currentRelativeTime = 0;
            this.timerId = NativeMethods.TimeSetEvent(this.timerInterval, 0, this.timerDelegate, ref ctx, 1);
        }

        /// <summary>
        /// Called when the agent is about to start running.
        /// </summary>
        public override void OnDeactivated()
        {
            base.OnDeactivated();
            NativeMethods.TimeKillEvent(this.timerId);
        }

        /// <summary>
        /// Called to control the timer (e.g. to pause/resume or skip/fast forward)
        /// </summary>
        /// <param name="message">pause/resume and/or skip/fast forward.</param>
        public void ReceiveControl(TimerControlMessage message)
        {
            this.isPaused = message.Pause;
            this.isSkipping = message.Skip;            
            if (message.Seek && message.SeekTime > this.currentRelativeTime)
            {                
                this.currentRelativeTime = message.SeekTime;
                TimerMessage timerMessage = new TimerMessage(this.currentRelativeTime);
                this.Publisher.Post(timerMessage);
            }
        }

        /// <summary>
        /// Wakes up every timerInterval to publish a new message. 
        /// </summary>
        /// <param name="timerID">The parameter is not used.</param>
        /// <param name="msg">The parameter is not used.</param>
        /// <param name="userCtx">The parameter is not used.</param>
        /// <param name="dw1">The parameter is not used.</param>
        /// <param name="dw2">The parameter is not used.</param>
        private void PublishTime(uint timerID, uint msg, UIntPtr userCtx, UIntPtr dw1, UIntPtr dw2)
        {
            if (this.isPaused && !this.isSkipping)
            {
                return;
            }            

            int controlFactor = 1;
            if (this.isSkipping)
            {
                controlFactor = this.isPaused ? SkipFactor : FastForwardFactor;
            }

            int period = (int)(this.timerInterval * this.timeAlteringFactor * controlFactor);
            this.currentRelativeTime += period;

            // stop skipping if in paused mode, because the intent is to jump one "frame" ahead and stop.
            this.isSkipping = this.isSkipping && !this.isPaused;

            // publish a new message. 
            TimerMessage message = new TimerMessage(this.currentRelativeTime);
            this.Publisher.Post(message);
        }
    }
}
